const fs = require("fs");
const path = require("path");
const config = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "config.json"), "utf8"));
const { execSync, exec } = require("child_process");
const { FFMPEG_PATH } = require("./utils/ffmpeg");
const { getFirstFileNameWithoutExt } = require("./utils/common");
const { mergeVideosOrderedCommand, mergeVideosOrderedByListCommand } = require("./utils/ffmpegCommands");
const { generatePageFlipVideo, generatePageFlipVideoCommandV2, addAudioToVideo } = require("./utils/ffmpegCommands_v2");
const { mergeVideosOrderedByList, mergeAudioVideo } = require("./utils/videoProcessor");
const encodeFormat = config.encodeFormat || "h264_nvenc";
const { createCanvas, loadImage } = require("canvas");
const { generateConcatVideoCommand } = require("./utils/ffmpegCommands");

const perPageFlipDuration = 0.5;
const perWordDuration = 2;
/**
 * 所有临时脚本都存储在此，最后一并删除
 */
const tempFiles = [];
// 设置临时工作空间为 config.temp对应的目录下的glenworld目录
const tempDir = path.join(config.temp, "glenworld");
// step 1: 从https://apps.glenworld.org/deployment/KTal8Q5HNNThIP06gS24/glenLearn/lesson/[x] 网站上下载资源，
//   从temp\glenworld\lesson_groups.json中读取数据，创建0~8个目录在tempDir下
for (let i = 1; i <= 8; i++) {
  fs.mkdirSync(path.join(tempDir, i.toString()), { recursive: true });
}
const lessons = JSON.parse(fs.readFileSync(path.join(tempDir, "lessons.json"), "utf8"));

function getLessonInfo(pLessonID) {
  for (const lessonID of Object.keys(lessons)) {
    console.log(` 正在处理课程 ${lessonID}...,pLessonID:${pLessonID}`);
    if (lessonID === pLessonID) {
      return lessons[lessonID];
    }
  }
  return null;
}
function getLessonName(pLessonID) {
  const lessonInfo = getLessonInfo(pLessonID);
  if (lessonInfo) {
    const intro = lessonInfo.introAudio;
    if (!intro) return getWordsByLessonID(pLessonID).words.join(",");
    return intro[intro.length - 1];
  }
  return null;
}
/**
 * 通过课程每个单词内容，创建课程封面
 * @param {string} lessonID 课程ID
 * @param {string} lessonDir 课程资源目录
 * @param {Array<Object>} lessonResource 课程资源信息
 * @param {string} outputPath 输出文件路径
 */
async function createCoverImage(lessonID, lessonDir, lessonResource, outputPath) {
  console.log("  正在创建课程封面...");
  const w = config.projectSettings.videoWidth;
  const h = config.projectSettings.videoHeight;
  const canvas = createCanvas(w, h);
  const ctx = canvas.getContext("2d");

  const inputDir = path.join(__dirname, "../input");
  let firstFramePath = path.resolve(inputDir, "first-frame-long.jpg");
  console.log(firstFramePath);
  const img = await loadImage(firstFramePath);
  ctx.drawImage(img, 0, 0, w, h);
  const titleH1 = `Words for kids`;
  let lessonName = getLessonName(lessonID);
  console.log("  课程名称:", lessonID, lessonName);
  if (!lessonName) lessonName = "";
  lessonName = lessonName.replace(/\_/g, " ");
  lessonName = lessonName.charAt(0).toUpperCase() + lessonName.slice(1);
  const lessonNameOrginal = lessonName + "";
  const titleH2 = `${lessonNameOrginal}`;
  // 在canvas的距离顶部28%的位置绘制titleH1，水平居中，字体为DynaPuff,字号大小为72
  ctx.font = `96px DynaPuff`;
  ctx.fillStyle = "orange";
  console.log("  绘制课程标题...");
  ctx.fillText(titleH1, w / 2 - ctx.measureText(titleH1).width / 2, h * 0.28);
  // 将课程的前6个页面绘制在canvas上，水平居中，垂直居中，字体为DynaPuff,字号大小为32，字体颜色深红色
  // 每个图片的大小随着总数变化
  // 如果有三个单词，则图片高度为config.projectSettings.videoHeight*80%/3 - 5px,高度等于宽度，在屏幕垂直居中，三个图片水平对齐
  // 如果有四个单词，则图片高度为config.projectSettings.videoHeight*80%/2 - 5px，上二、下二图片
  // 如果有5个单词，则图片高度为config.projectSettings.videoHeight*80%/3 - 5px，上三、下二图片
  // 如果有6个单词，则图片高度为config.projectSettings.videoHeight*80%/3 - 5px，上三、下三图片
  // 如果有7个单词，则图片高度为config.projectSettings.videoHeight*80%/4 - 5px，上四、下三图片
  // 如果有8个单词，则图片高度为config.projectSettings.videoHeight*80%/4 - 5px，上四、下四图片
  let subImgSize = 0;
  if (lessonResource.length > 6) {
    subImgSize = Math.min((h * 0.8) / 4 - 5, (w * 0.8) / 4 - 5);
    let startX = w * 0.1;
    let startY = h * 0.5 - subImgSize;
    for (let i = 0; i < lessonResource.length && i < 8; i++) {
      const word = lessonResource[i].word;
      const imgPath = path.join(lessonDir, word, `${word}_medium.png`);
      console.log("  正在处理单词图片:", imgPath);
      const img = await loadImage(imgPath);
      let x = startX;
      let y = startY;
      if (lessonResource.length === 7 && i > 3) {
        x = startX + (i % 4) * subImgSize + subImgSize / 2 + 5;
      } else x = startX + (i % 4) * subImgSize + 5;
      if (i > 3) y = startY + subImgSize + 5;
      console.log("  绘制单词图片:", i, x, y, subImgSize);
      ctx.drawImage(img, x, y, subImgSize, subImgSize);
    }
  } else if (lessonResource.length === 5 || lessonResource.length === 6 || lessonResource.length === 3) {
    subImgSize = Math.min((h * 0.8) / 3 - 5, (w * 0.8) / 3 - 5);
    let startX = w * 0.1;
    let startY = h * 0.5 - subImgSize;
    for (let i = 0; i < lessonResource.length; i++) {
      const word = lessonResource[i].word;
      const imgPath = path.join(lessonDir, word, `${word}_medium.png`);
      console.log("  正在处理单词图片:", imgPath);
      const img = await loadImage(imgPath);
      let x = startX;
      let y = startY;
      console.log(lessonResource.length);
      if (lessonResource.length === 5 && i > 2) {
        x = startX + (i % 3) * subImgSize + subImgSize / 2 + 5;
      } else x = startX + (i % 3) * subImgSize + 5;
      if (i > 2) y = startY + subImgSize + 5;
      if(lessonResource.length === 3)
        y = startY + subImgSize / 2;
      ctx.drawImage(img, x, y, subImgSize, subImgSize);
    }
  } else {
    subImgSize = Math.min((h * 0.8) / 2 - 5, (w * 0.8) / 2 - 5);
    let startX = w * 0.1;
    let startY = h * 0.5 - subImgSize / 2;
    for (let i = 0; i < lessonResource.length; i++) {
      const word = lessonResource[i].word;
      const imgPath = path.join(lessonDir, word, `${word}_medium.png`);
      console.log("  正在处理单词图片:", imgPath);
      const img = await loadImage(imgPath);
      let x = startX;
      let y = startY;
      x = startX + (i % 2) * subImgSize + 5;
      if (i > 2) y = startY + subImgSize + 5;
      ctx.drawImage(img, x, y, subImgSize, subImgSize);
    }
  }

  // 在canvas的距离顶部66%的位置绘制titleH2，水平居中，字体为DynaPuff,字号大小为32，字体颜色深红色
  ctx.font = `48px DynaPuff`;
  ctx.fillStyle = "red";
  console.log("  绘制课程次标题...");
  const title2PosX = w / 2 - ctx.measureText(titleH2).width / 2;
  const title2PosY = h * 0.77;
  console.log(" ", titleH2, title2PosX, title2PosY);
  ctx.fillText(titleH2, title2PosX, title2PosY);
  // 保存canvas为png文件，到outputPath
  fs.writeFileSync(outputPath, canvas.toBuffer("image/png"));
  console.log("  课程封面已保存到", outputPath);
}

/**
 * 根据lessonID 在tempDir/glenworld/下0-9/lessonID文件夹，查找其下的子文件夹名称就是每个单词
 * @param {Number} lessonID 课程编号
 */
function getWordsByLessonID(lessonID) {
  for (let i = 0; i < 10; i++) {
    const lessonDir = path.join(tempDir, i.toString(), lessonID.toString());
    console.log("  lessonDir:", lessonDir);
    if (fs.existsSync(lessonDir)) {
      // 获取words,读取lessionDir下的所有文件夹名称
      const words = fs.readdirSync(lessonDir).filter(file => {
        return fs.statSync(path.join(lessonDir, file)).isDirectory();
      });
      return { words, lessonDir };
    }
  }
  const words = [];
  let lessonDir = "";
  return { words, lessonDir };
}
async function getWordImgPath(lessonDir, word) {
  const wordDir = path.join(lessonDir, word);
  // 查找wordDir目录下的png，返回第一个
  const wordImg = fs.readdirSync(wordDir).find(file => file.endsWith(".png"));
  // 用canvas 绘制一个长宽等于config规定的projectSettings的宽高白色图片
  const canvas = createCanvas(config.projectSettings.videoWidth, config.projectSettings.videoHeight);

  const ctx = canvas.getContext("2d");
  const paper = await loadImage(path.join(tempDir, "paper.jpeg"));
  ctx.drawImage(paper, 0, 0, config.projectSettings.videoWidth, config.projectSettings.videoHeight);
  // 将ctx填充为白色
  // ctx.fillStyle = "white";
  //ctx.fillRect(0, 0, config.projectSettings.videoWidth, config.projectSettings.videoHeight);
  // 将wordImg绘制在canvas上，如果图片宽度大于视频宽度，则将图片宽度缩放为视频宽度的0.8倍保持水平，垂直居中
  if (wordImg) {
    // 创建一个Image对象，用于保存图片；注意这里是nodejs环境，非浏览器
    const img = await loadImage(path.join(wordDir, wordImg));
    if (img.width > config.projectSettings.videoWidth) {
      console.log("  图片宽度大于视频宽度，将图片宽度缩放为视频宽度的0.8倍保持水平，垂直居中");
      // 图片宽度大于视频宽度，将图片宽度缩放为视频宽度的0.8倍保持水平，垂直居中
      const newWidth = config.projectSettings.videoWidth * 0.8;
      const newHeight = (canvas.height * newWidth) / canvas.width;
      ctx.drawImage(img, (config.projectSettings.videoWidth - newWidth) / 2, (config.projectSettings.videoHeight - newHeight) / 2, newWidth, newHeight);
    } else {
      console.log("  图片宽度小于等于视频宽度，将图片绘制在canvas上");
      // 图片宽度小于等于视频宽度,将图片宽度缩放为视频宽度的0.8倍保持水平，垂直居中
      const newWidth = canvas.width * 0.8;
      const newHeight = (img.height / img.width) * newWidth;
      ctx.drawImage(img, (config.projectSettings.videoWidth - newWidth) / 2, (config.projectSettings.videoHeight - newHeight) / 2, newWidth, newHeight);
    }
  }
  // 将word,写到canvas底部1/3处，word需要经过smartTextWrap处理
  ctx.font = `${config.projectSettings.videoHeight / 3}px ${config.projectSettings.textStyles.fontFamily}`;
  ctx.fillStyle = config.projectSettings.textStyles.color || "black";
  const finalTxt = smartTextWrap(word, canvas, ctx);
  const textWidth = ctx.measureText(finalTxt).width;
  ctx.fillText(finalTxt, (config.projectSettings.videoWidth - textWidth) / 2, config.projectSettings.videoHeight * 0.8);
  //将图片保存为wordImg同目录_withBg.jpg
  const wordImgWithBg = path.join(wordDir, `${word}_with_background.jpg`);
  fs.writeFileSync(wordImgWithBg, canvas.toBuffer("image/jpeg"));
  return wordImgWithBg;
}

function getWordAudioPath(lessonDir, word) {
  const wordDir = path.join(lessonDir, word);
  // 获取wordDir目录下的mp3，返回第一个
  const wordAudio = fs.readdirSync(wordDir).find(file => file.endsWith(".mp3"));
  return wordAudio ? path.join(wordDir, wordAudio) : null;
}

/**
 * 智能文本换行
 * @param {string} str - 要处理的字符串
 * @param {Canvas} canvas - 画布对象
 * @param {CanvasRenderingContext2D} ctx - 画布上下文对象
 * @returns {string} 处理后的字符串
 */
function smartTextWrap(str, canvas, ctx) {
  // 计算宽度阈值（canvas宽度的80%）
  const widthThreshold = canvas.width * 0.8;
  ctx.font = `64px italic`;
  // 检查字符串宽度是否超过阈值
  const textWidth = ctx.measureText(str).width;

  // 如果未超过阈值，直接返回原字符串
  if (textWidth <= widthThreshold) {
    return str;
  }

  // 从中间位置开始寻找空格
  let middle = Math.floor(str.length / 2);
  let splitIndex = -1;

  // 先向字符串末尾方向查找空格
  for (let i = middle; i < str.length; i++) {
    if (str[i] === " ") {
      splitIndex = i;
      break;
    }
  }

  // 如果未找到，再向字符串开始方向查找空格
  if (splitIndex === -1) {
    for (let i = middle; i >= 0; i--) {
      if (str[i] === " ") {
        splitIndex = i;
        break;
      }
    }
  }

  // 如果找不到空格，直接在中间位置分割
  if (splitIndex === -1) {
    splitIndex = middle;
  }

  // 分割字符串为两部分
  const part1 = str.substring(0, splitIndex);
  const part2 = str.substring(splitIndex + 1); // +1 是为了跳过空格

  // 递归处理两部分，然后用换行符连接
  const wrappedPart1 = smartTextWrap(part1, canvas, ctx);
  const wrappedPart2 = smartTextWrap(part2, canvas, ctx);

  return `${wrappedPart1}\n${wrappedPart2}`;
}

// 更加单词获取对应的句子音频和图片以及字幕
// 在tempDir/describe_it/下找到名称为word的目录
// 如果找到，则返回tempDir/describe_it/describe_it/下的png图片和mp3音频,并且把音频的名称作为字幕字符串返回
function getImageAndAudio(word) {
  const describeItDir = path.join(`${tempDir}/describe_it`);
  // 查找describeItDir的下下级目录，如果匹配单词word名称，则返回与word并列的目录
  const describeItDirSubDir = fs.readdirSync(describeItDir, { recursive: false });
  console.log("  开始搜索单词资源...,在 ", describeItDir, "目录...");
  const dirsCoundUse = [];
  for (const dir of describeItDirSubDir) {
    // console.log("  正在搜索单词资源...", dir);
    const subDirs = fs.readdirSync(path.join(describeItDir, dir), { recursive: false });
    for (const subDir of subDirs) {
      // console.log("  正在搜索单词资源...", dir, "/", subDir, "subDir:", subDir);

      if (subDir === word) {
        const recourceDir = path.join(describeItDir, dir, "describe_it");
        dirsCoundUse.push(recourceDir);
      }
    }
  }
  if (dirsCoundUse.length > 0) return getImageAndAudioRandom(dirsCoundUse);
  else return { imagePath: null, audioPath: null, text: null };
}

function getImageAndAudioRandom(recourceDirs) {
  // 找到匹配单词的目录，返回该目录下的png图片和mp3音频
  const recourceDir = recourceDirs[Math.floor(Math.random() * recourceDirs.length)];
  console.log("  找到匹配单词的目录，返回该目录下的png图片和mp3音频...", recourceDir);
  const imagePath = getFirstFileNameWithoutExt(recourceDir, ".png") + ".png";
  console.log("    找到图片，imagePath:", imagePath);
  const audioPath = getFirstFileNameWithoutExt(recourceDir, ".mp3") + ".mp3";
  console.log("    找到音频，audioPath:", audioPath);
  // 将audioPath的下划线转化为空格，然后将首字母大写作为描述
  let text = audioPath.replace(".mp3", "").replace(/_/g, " ");
  text = text.charAt(0).toUpperCase() + text.slice(1);
  console.log("    将audioPath的下划线转化为空格，然后将首字母大写作为描述，text:", text);
  const iPath = path.join(recourceDir, imagePath);
  const aPath = path.join(recourceDir, audioPath);
  console.log("    iPath:", iPath);
  console.log("    aPath:", aPath);
  return { imagePath: iPath, audioPath: aPath, text: text };
}

/**
 * 根据音频文件路径获取音频长度
 * @param {String} audioPath 音频路径
 */
function getAudioDuration(audioPath) {
  try {
    // 处理路径，确保在Windows环境下正确识别
    const fullFilePath = path.resolve(audioPath);

    // 执行ffmpeg命令，注意转义路径中的空格
    const command = `"${FFMPEG_PATH}" -i "${fullFilePath}" 2>&1`;
    let output;

    try {
      // 尝试执行命令，忽略错误码（因为ffmpeg在仅查看信息时会返回非0码）
      output = execSync(command, {
        encoding: "utf8",
        stdio: ["pipe", "pipe", "pipe"], // 合并输出流
      });
    } catch (err) {
      // 捕获错误，但使用错误输出中的内容（因为ffmpeg信息在stderr）
      output = err.stdout + err.stderr;
    }

    // 正则表达式匹配时长信息
    const durationMatch = output.match(/Duration: (\d+:\d+:\d+\.\d+)/);
    if (!durationMatch) {
      throw new Error("无法提取时长信息: " + output.substring(0, 200));
    }

    const durationStr = durationMatch[1];
    const [hours, minutes, seconds] = durationStr.split(":").map(Number);

    // 转换为总秒数
    const totalSeconds = hours * 3600 + minutes * 60 + seconds;
    return Math.round(totalSeconds * 100) / 100;
  } catch (error) {
    console.error("获取时长失败:", error.message);
    process.exit(1);
  }
}

//
/**
 * setp 4: 创建视频开头
 * 参数：单个单词的图片、音频资源
 * @param {Array<Ojbect>} lessonResourcesItem
 * @param {String} lessonResourcesItem.word
 * @param {String}  lessonResourcesItem.imgPath
 * @param {String}  lessonResourcesItem.audioPath
 * @param {String}  lessonResourcesItem.wordDuration
 * @param {String}  lessonResourcesItem.allImagesWithBg
 * @param {String}  lessonResourcesItem.assAudioFiles
 * @param {String}  lessonResourcesItem.allAudioLengths
 */
async function createVideoStart(lessonDir, lessonResource,coverPath, outputPath) {
  // 获取config.inputStartVoice对应的音频文件
  const startVoicePath = path.join(__dirname, "..", config.inputStartVoice);
  console.log(`  开始语音文件为：${startVoicePath}`);
  const wordImanges = [];
  for (let i = 0; i < lessonResource.length; i++) {
    wordImanges.push(lessonResource[i].imgPath);
  }
  // generatePageFlipVideo(wordImanges, {
  //   durationPerImage: 3,
  //   transitionDuration: 1,
  //   outputPath: outputPath,
  // });
  const mp4Files = [];
  // 处理首页和第一帧的动画
  // 创建page1和page2的翻页帧图片
  const page1Img = await loadImage(coverPath);
  const word0 = lessonResource[0];
  const page2Img = await loadImage(word0.imgPath);
  const pngPageDir = path.join(__dirname, "../../png-page");
  // page1Img的jpg保存为jpg
  await saveImageAsJpg(page1Img, path.join(pngPageDir, "images", `1.jpg`));
  await saveImageAsJpg(page2Img, path.join(pngPageDir, "images", `2.jpg`));
  // 启动../png-page/server.js
  const execSyncCommandStartServer = `node ${path.join(pngPageDir, "run.bat")}`;
  // 不等待返回
  exec(execSyncCommandStartServer, { stdio: "inherit" });
  // 运行../png-page/capture.js
  const execSyncCommand = `node ${path.join(pngPageDir, "capture.js")}`;
  execSync(execSyncCommand, { stdio: "inherit" });
  // 将生成的文件（在../png-page/frames）拷贝到tempDir/frames目录
  const sourceDir = path.join(pngPageDir, "/animation-screenshots");
  const targetDir = path.join(tempDir, "temp-frames");
  copyDir(sourceDir, targetDir);
  const tmpVideoDir = lessonDir;
  !fs.existsSync(tmpVideoDir) && fs.mkdirSync(tmpVideoDir);
  const mp4FilePath = path.join(tmpVideoDir, `v_${0}_${0}.mp4`);
  // 将targetDir下的图片转换为mp4视频
  convertImagesToMp4(targetDir, mp4FilePath);
  mp4Files.push(mp4FilePath);
  // 处理各页面之间的动画
  for (let i = 0; i < lessonResource.length - 1; i++) {
    const word1 = lessonResource[i];
    const word2 = lessonResource[i + 1];
    // 创建page1和page2的翻页帧图片
    const page1Img = await loadImage(word1.imgPath);
    const page2Img = await loadImage(word2.imgPath);
    // page1Img的jpg保存为jpg
    await saveImageAsJpg(page1Img, path.join(pngPageDir, "images", `1.jpg`));
    await saveImageAsJpg(page2Img, path.join(pngPageDir, "images", `2.jpg`));
    // 启动../png-page/server.js
    const execSyncCommandStartServer = `node ${path.join(pngPageDir, "run.bat")}`;
    // 不等待返回
    exec(execSyncCommandStartServer, { stdio: "inherit" });
    // 运行../png-page/capture.js
    const execSyncCommand = `node ${path.join(pngPageDir, "capture.js")}`;
    execSync(execSyncCommand, { stdio: "inherit" });
    // 将生成的文件（在../png-page/frames）拷贝到tempDir/frames目录
    copyDir(sourceDir, targetDir);
    const mp4FilePath = path.join(tmpVideoDir, `v_${i}_${i + 1}.mp4`);
    // 将targetDir下的图片转换为mp4视频
    convertImagesToMp4(targetDir, mp4FilePath);
    word1.flipMp4File = mp4FilePath;
    mp4Files.push(mp4FilePath);
  }
  // 将mp4Files中的视频合并为一个视频
  mergeMp4Files(mp4Files, lessonDir, outputPath);
}
/**
 * 将源目录的文件，全部拷贝到目标目录
 * @param {String} sourceDir 源目录
 * @param {String} targetDir 目标目录
 */
function copyDir(sourceDir, targetDir) {
  if (!fs.existsSync(targetDir)) {
    fs.mkdirSync(targetDir, { recursive: true });
  }
  // 遍历sourceDir下的文件
  fs.readdirSync(sourceDir).forEach(file => {
    const sourceFilePath = path.join(sourceDir, file);
    const targetFilePath = path.join(targetDir, file);
    if (fs.statSync(sourceFilePath).isFile()) {
      fs.copyFileSync(sourceFilePath, targetFilePath);
    }
  });
}
/**
 * 将mp4Files中的视频合并为一个视频
 * @param {String} mp4Files mp4文件路径数组
 * @param {String } outputPath 输出视频路径
 */
function mergeMp4Files(mp4Files, lessonDir, outputPath) {
  const mp4FilesDir = lessonDir;
  const listFilePath = path.join(mp4FilesDir, "list_Start.txt");
  let listContent = "";
  for (let i = 0; i < mp4Files.length; i++) {
    const mp4File = mp4Files[i];
    const fileName = path.basename(mp4File);
    listContent += `file '${fileName}'\n`;
    listContent += `duration ${perPageFlipDuration}\n`;
  }
  fs.writeFileSync(listFilePath, listContent);
  const command = mergeVideosOrderedByListCommand(listFilePath, outputPath);
  execSync(command, { stdio: "inherit" });
}
/**
 * 通过ffmpeg 将图片转化为视频
 * 将图片转化为视频，大小为config.projectionSettings.projectionWidth x config.projectionSettings.projectionHeight
 * @param {String} sourceDir 图片目录
 * @param {String} outputPath 输出视频目录
 */
function convertImagesToMp4(sourceDir, outputPath) {
  const w = config.projectSettings.videoWidth;
  const h = config.projectSettings.videoHeight;
  const outputFileWithoutAudio = outputPath.replace(".mp4", "_no_audio.mp4");
  const command = `"${FFMPEG_PATH}" -r ${perPageFlipDuration * 60} -i ${sourceDir}/frame-%04d.png -c:v libx264 -pix_fmt yuv420p -vf "scale=${w}:${h}" -y ${outputFileWithoutAudio}`;
  execSync(command, { stdio: "inherit" });
  // 在视频中加入音频../input\voice\changepage_short.mp3,最终视频长度不变
  const commandAddAudio = `"${FFMPEG_PATH}" -i ${outputFileWithoutAudio} -i ${config.inputDirVoice}/changepage_short.mp3 -c:v copy -c:a aac -y ${outputPath}`;
  execSync(commandAddAudio, { stdio: "inherit" });
}
/**
 * 保存图片为jpg
 * @param {Image} img - 图片对象
 * @param {string} outputPath - 输出路径
 */
async function saveImageAsJpg(img, outputPath) {
  const canvas = createCanvas(img.width, img.height);
  const ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  const buffer = canvas.toBuffer("image/jpeg");
  fs.writeFileSync(outputPath, buffer);
}
/**
 * setp 4: 创建视频内容
 * 参数：单个单词的图片、音频资源
 * @param {Array<Ojbect>} lessonResourcesItem
 * @param {String} lessonResourcesItem.word
 * @param {String}  lessonResourcesItem.imgPath
 * @param {String}  lessonResourcesItem.audioPath
 * @param {String}  lessonResourcesItem.wordDuration
 * @param {String}  lessonResourcesItem.allImagesWithBg
 * @param {String}  lessonResourcesItem.assAudioFiles
 * @param {String}  lessonResourcesItem.allAudioLengths
 */
async function createVideoContent(lessonID, lessonDir, lessonResource, outputPath) {
  console.log("  开始处理课程...,", lessonID);
  // 根据lessonID 获取单词列表
  const { words } = getWordsByLessonID(lessonID);
  console.log("  words:", words);
  if (words.length === 0) return;
  console.log("  lessonDir:", lessonDir);
  // 遍历每一个单词实现：
  const wordFinalVideosPath = [];
  const listFile = path.join(lessonDir, "list.txt");
  console.log("  listFile:", listFile);
  let listContent = "";
  tempFiles.push(listFile);
  let inputDuration = 0.0;
  for (let i = 0; i < words.length; i++) {
    const word = words[i];
    console.log("  word:", word);
    // 获取单词的图片
    const imgPath = await getWordImgPath(lessonDir, word);
    // 获取单词的音频
    const audioPath = getWordAudioPath(lessonDir, word);
    const videoPath = path.join(lessonDir, `${word}.mp4`);
    tempFiles.push(videoPath);
    // step 3.1 图片转视频，将图片转为长1秒的视频
    console.log(`  2. 图片转视频...,videoPath:${videoPath}`);
    convertImageToVideo(imgPath, videoPath);
    // step 3.1.1 将视频加入单词声音，总时长为视频时长，保存为 temp/glenworld/lessonID/[word_a]_no_subtitle.mp4
    const videoPathNoSubtitle = path.join(lessonDir, `${word}_no_subtitle.mp4`).replace(/ /g, "_");
    tempFiles.push(videoPathNoSubtitle);
    console.log(`  3. 将视频加入单词声音...`);
    mergeAudioVideo(videoPath, audioPath, videoPathNoSubtitle, perWordDuration);
    // step 3.2 temp/glenworld/x/output_no_subtitle.mp4 加入文字，保存为 temp/glenworld/lessonID/[word_a]_with_subtitle.mp4
    const videoPathWithSubtitle = path.join(lessonDir, `${word}_with_subtitle.mp4`).replace(/ /g, "_");
    tempFiles.push(videoPathWithSubtitle);
    console.log(`  3.2 将视频加入文字...`);
    addTextToVideo(word, videoPathNoSubtitle, videoPathWithSubtitle);
    wordFinalVideosPath.push(videoPathNoSubtitle);
    wordFinalVideosPath.push(videoPathWithSubtitle);
    console.log(`  开始获取单词资源...`);

    const wordSentenceVideoPath = path.join(lessonDir, `${word}_sentence.mp4`);
    tempFiles.push(wordSentenceVideoPath);
    if (lessonResource[i] && lessonResource[i].flipMp4File) {
      const flipMp4File = lessonResource[i].flipMp4File;
      listContent += `file '${path.basename(flipMp4File)}'\n`;
      listContent += `duration ${0.5}\n`;
    }
    listContent += `file '${path.basename(videoPathNoSubtitle)}'\n`;
    inputDuration = getAudioDuration(videoPathNoSubtitle);
    listContent += `duration ${inputDuration}\n`;
    listContent += `file '${path.basename(videoPathWithSubtitle)}'\n`;
    inputDuration = getAudioDuration(videoPathWithSubtitle);
    listContent += `duration ${inputDuration}\n`;
    const res = getImageAndAudio(word);
    if (res.audioPath) {
      createWordSentenceVideo(lessonDir, word, res, wordSentenceVideoPath);
      listContent += `file '${path.basename(wordSentenceVideoPath)}'\n`;
      inputDuration = getAudioDuration(wordSentenceVideoPath);
      listContent += `duration ${inputDuration}\n`;
    }
  }
  const finallVideoPath = path.join(lessonDir, "finall.mp4");
  tempFiles.push(finallVideoPath);
  console.log(`  3.3 将视频合并...`);
  fs.writeFileSync(listFile, listContent, { encoding: "utf-8" });
  const outputMergeContentPath = path.join(lessonDir, `${lessonID}_MergeContent.mp4`);
  tempFiles.push(outputMergeContentPath);
  tempFiles.push(listFile);

  // 使用更适合处理统一视频格式的参数
  let videoCommand = generateConcatVideoCommand(listFile.replace(/\\/g, "/"), outputMergeContentPath, true);
  // console.log('\n  执行视频生成命令:', videoCommand);
  console.log("\n  执行视频生成命令...");

  // 将命令写入临时脚本文件执行，避免命令行太长问题
  const tempDir = path.dirname(listFile.replace(/\\/g, "/"));
  const scriptPath = path.join(tempDir, "concat_script.bat");
  fs.writeFileSync(scriptPath, videoCommand, "utf-8");
  execSync(`"${scriptPath}"`, { stdio: "ignore" });
  // 执行完后删除临时脚本文件
  if (fs.existsSync(scriptPath)) {
    fs.unlinkSync(scriptPath);
  }

  // mergeVideosOrderedByList(listFile.replace(/\\/g, "/"), outputMergeContentPath, true);
  const style = `:x=(w-text_w)/2:y=h*0.1:fontsize=64:fontcolor=yellow`;
  let lessonName = getLessonName(lessonID);
  if (!lessonName) lessonName = "";
  lessonName = lessonName.replace(/\_/g, " ");
  lessonName = lessonName.charAt(0).toUpperCase() + lessonName.slice(1);
  // 将lessonName进行换行处理，如果lessonName的空格数超过三个，则将最中间的一个空格替换为换行符
  lessonName = replaceMiddleSpace(lessonName);

  let lessonNameTempFile = path.join(lessonDir, "lessonName.txt");
  lessonNameTempFile = lessonNameTempFile.replace(/\\/g, "/");
  tempFiles.push(lessonNameTempFile);
  fs.writeFileSync(lessonNameTempFile, lessonName, "utf-8");
  addTextToVideo(lessonNameTempFile, outputMergeContentPath, outputPath, style);
  console.log("  视频内容创建完成...,输出位置：", outputPath);
}
function replaceMiddleSpace(str) {

  // 将首字母大写
  str = str.charAt(0).toUpperCase() + str.slice(1);
  console.log("  正在处理句子:", str);
  const canvas = createCanvas(config.projectSettings.videoWidth, config.projectSettings.videoHeight);
  const ctx = canvas.getContext("2d");
  return smartTextWrap(str, canvas, ctx);
}
function convertImageToVideo(imagePath, videoPath, dur) {
  let configVideoWidth = config.projectSettings.videoWidth || 1920;
  let configVideoHeight = config.projectSettings.videoHeight || 1080;
  const duration = dur || perWordDuration;
  // 使用FFmpeg将图片转换为视频，保持正确的宽高比
  try {
    const convertCommand = `"${FFMPEG_PATH}" -y -loglevel quiet -loop 1 -i "${imagePath}" -vf "scale=w=${configVideoWidth}:h=${configVideoHeight}:force_original_aspect_ratio=decrease,crop=w=min(${configVideoWidth}\\,iw):h=min(${configVideoHeight}\\,ih):x=(iw-min(${configVideoWidth}\\,iw))/2:y=(ih-min(${configVideoHeight}\\,ih))/2,pad=w=${configVideoWidth}:h=${configVideoHeight}:x=(ow-iw)/2:y=(oh-ih)/2:color=black" -c:v ${encodeFormat} -t ${duration} -pix_fmt yuv420p -r 30 "${videoPath}"`;
    execSync(convertCommand);
  } catch (error) {
    console.error("  图片转换为视频时出错:", error.message);
    process.exit(1);
  }
  console.log("  视频已生成:", videoPath);
}

function addTextToVideo(text, videoPath, outputPath, style) {
  text = text.replace(/_color/g, "");
  text = "";
  if (!style) style = `:x=(w-text_w)/2:y=h*0.8:fontsize=128:fontcolor=white:box=1:boxcolor=red:boxborderw=10`;
  let command = `${FFMPEG_PATH} -y -loglevel quiet -i "${videoPath}" -vf "drawtext=text='${text}'${style}" "${outputPath}"`;
  if (text.endsWith(".txt")) {
    command = `${FFMPEG_PATH} -y -loglevel quiet -i "${videoPath}" -vf "drawtext=textfile='${text}'${style}" "${outputPath}"`;
  }
  console.log("  执行命令:", command);
  try {
    execSync(command);
  } catch (error) {
    console.error("  添加文字到视频时出错:", error.message);
    process.exit(1);
  }
}
/**
 * 单个课程的图片、音频资源
 * @type lessonResources.item
 * {
 *   word: "word",
 *   allImagesWithBg,
 *   assAudioFiles,
 *   allAudioLengths,
 * }
 */
const lessonResources = [];
// step 3: 创建视频内容部分
async function createContent(lessonID) {
  lessonResources.length = 0;
  console.log("  开始处理课程...,", lessonID);
  // 根据lessonID 获取单词列表
  const { words, lessonDir } = getWordsByLessonID(lessonID);
  console.log("  words:", words);
  if (words.length === 0) return;
  console.log("  lessonDir:", lessonDir);
  // 遍历每一个单词实现：
  const listFile = path.join(lessonDir, "list.txt");
  console.log("  listFile:", listFile);
  tempFiles.push(listFile);
  for (let i = 0; i < words.length; i++) {
    const word = words[i];
    console.log("  word:", word);
    // 获取单词的图片
    const imgPath = await getWordImgPath(lessonDir, word);
    // 获取单词的音频
    const audioPath = getWordAudioPath(lessonDir, word);
    const videoPath = path.join(lessonDir, `${word}.mp4`);
    tempFiles.push(videoPath);

    // 获取音频时长
    const wordDuration = getAudioDuration(audioPath);

    const res = getImageAndAudio(word);

    const lessonResourcesItem = {
      word,
      imgPath,
      audioPath,
      wordDuration,
      sentenceRes: res,
    };
    lessonResources.push(lessonResourcesItem);
  }
  const outputPath = path.join(lessonDir, `output.mp4`);
  // 通过lessonResourcesItem创建课程视频
  await createWordVideo(lessonDir, lessonID, lessonResources, outputPath);
}
/**
 * 单个单词的图片、音频资源
 * @param {Array<Ojbect>} lessonRes
 * @param {String} lessonResourcesItem.word
 * @param {String}  lessonResourcesItem.imgPath
 * @param {String}  lessonResourcesItem.audioPath
 * @param {String}  lessonResourcesItem.wordDuration
 * @param {String}  lessonResourcesItem.allImagesWithBg
 * @param {String}  lessonResourcesItem.assAudioFiles
 * @param {String}  lessonResourcesItem.allAudioLengths
 */
async function createWordVideo(lessonDir, lessonID, lessonRes, outputPath) {
  console.log("  开始处理课程：", lessonID);
  const coverPath = path.join(lessonDir, `${lessonID}_cover.png`);
  await createCoverImage(lessonID, lessonDir, lessonRes, coverPath);
  // 创建视频
  // 创建视频开头
  const startVideoPath = path.join(lessonDir, `${lessonID}_start.mp4`);
  // tempFiles.push(startVideoPath);
  await createVideoStart(lessonDir, lessonRes,coverPath, startVideoPath);
  // 创建视频内容
  const contentVideoPath = path.join(lessonDir, `${lessonID}_content.mp4`);
  // tempFiles.push(contentVideoPath);
  await createVideoContent(lessonID, lessonDir, lessonRes, contentVideoPath);
  // 拼接开头和内容
  const command = mergeVideosOrderedCommand([startVideoPath, contentVideoPath], outputPath);
  const scriptPath = path.join(lessonDir, "concat_script.bat");
  fs.writeFileSync(scriptPath, command, "utf-8");
  console.log("  开始拼接视频...");
  execSync(`"${scriptPath}"`, { stdio: "ignore" });
  console.log("  视频处理完成...");
}

async function main() {
  console.log("  开始处理...");
  await createContent("8");
  // 遍历清理 tempFiles中存储的文件
  for (const file of tempFiles) {
    if (fs.existsSync(file)) {
      // fs.unlinkSync(file);
    }
  }
}

main();
